Stateless Backend 可以翻成:
無狀態後端。
這裡的「無狀態」不是代表 Backend 完全沒有資料,而是:
Backend 不依賴某一台 Server 自己記住使用者或請求的狀態。
換句話說,每一個 Request 都應該攜帶足夠的資訊,讓任何一台 Backend 都能獨立處理。
例如現在有三台 Backend:
Backend 1
Backend 2
Backend 3
如果架構是 Stateless,那麼:
Request 1 → Backend 1
Request 2 → Backend 3
Request 3 → Backend 2
都不應該有問題。
因為三台 Backend 理論上具有相同的程式、相同的設定,也能存取相同的 Database、Redis 或其他外部服務。
先從相反的 Stateful Backend 來理解。
假設使用者登入後,Backend 1 直接把登入狀態存在自己的 Memory:
Backend 1 Memory
user_123 = logged_in
第一次 Request:
User
│
▼
Load Balancer
│
▼
Backend 1
登入成功
下一次 Request:
User
│
▼
Load Balancer
│
▼
Backend 2
Backend 2 的 Memory 中沒有:
user_123
因此 Backend 2 可能會認為這個使用者沒有登入。
這種情況表示:
Request 必須回到 Backend 1
才能正常工作
也就是 Backend 1 保存了某些只有它自己知道的 State。
這就是 Stateful。
架構會變成:
Backend 1
└── user A session
Backend 2
└── user B session
Backend 3
└── user C session
每台 Backend 的狀態不同。
Stateless 的做法,就是不要把重要狀態只放在單一 Backend 的 Memory。
例如 Session 可以放到 Redis:
Redis
session:user_123
│
▼
logged_in
三台 Backend 都可以讀:
Backend 1 ─┐
Backend 2 ─┼──→ Redis
Backend 3 ─┘
因此:
Request 1 → Backend 1
Request 2 → Backend 3
Request 3 → Backend 2
每台都可以查到:
session:user_123
Backend 就不再依賴:
「上一個 Request 是哪台 Server 處理的」
這就是 Stateless 的重要精神。
JWT 是一個很典型的例子。
假設登入成功後,Backend 回傳:
JWT Token
裡面可能包含:
user_id
role
expiration
Browser 之後每次呼叫 API 都帶:
Authorization: Bearer <token>
例如:
Request 1 → Backend 1
Authorization: Bearer abc...
Backend 1 可以驗證 Token。
下一次:
Request 2 → Backend 3
Authorization: Bearer abc...
Backend 3 同樣可以驗證。
只要 Backend 都有相同的簽章驗證設定,就不需要知道:
這個使用者上一個 Request
到底去了哪一台 Backend
因此:
Browser
│
│ JWT
▼
Load Balancer
│
├── Backend 1 ✓
├── Backend 2 ✓
└── Backend 3 ✓
任何 Backend 都能處理。
這裡容易產生一個誤解。
Stateless 並不是說:
Backend 完全不能使用 RAM
當然可以。
例如:
def calculate_price():
result = 100 + 20
return result
這些程式執行中的暫時資料,本來就會放在 Memory。
問題在於:
下一個 Request 是否依賴這些 Memory 資料。
例如這種情況通常沒有問題:
Request
│
▼
Backend
│
├── 建立變數
├── 計算
└── 回傳 Response
Request 結束後資料消失也沒關係。
但如果:
Request 1
│
▼
Backend 1 Memory
存入 user_state
然後:
Request 2
必須依賴 user_state
就開始變成 Stateful。
如果 Backend 是 Stateless:
Backend 1
Backend 2
Backend 3
其中 Backend 2 突然掛掉:
Backend 1 ✓
Backend 2 X
Backend 3 ✓
問題相對比較小。
因為使用者的重要資料並沒有只存在 Backend 2。
Load Balancer 只需要停止送 Request 給 Backend 2:
Load Balancer
/ \
▼ ▼
Backend 1 Backend 3
其他 Backend 還是可以繼續工作。
這就是 Stateless 很重要的一個概念:
Backend Server 本身應該盡可能是可以被替換的。
也就是:
Server 掛掉
│
▼
換一台新的
│
▼
服務繼續
而不是:
Server 掛掉
│
▼
使用者的重要狀態也一起消失
Auto Scaling 可以翻成:
自動擴縮容。
意思是系統可以根據目前的負載,自動增加或減少 Backend 數量。
例如平常流量不高:
Backend 1
Backend 2
但到了中午活動開始:
CPU > 80%
Request 大量增加
系統自動增加 Backend:
Backend 1
Backend 2
Backend 3
Backend 4
Backend 5
等流量降低後:
CPU < 30%
再減少機器:
Backend 1
Backend 2
這就是 Auto Scaling。
假設 Backend 是 Stateful。
目前:
Backend 1
Backend 2
Backend 3
而 Backend 3 裡面保存:
User A Session
User B Shopping Cart
Temporary Data
現在 Auto Scaling 判斷流量降低,想把 Backend 3 關掉:
Backend 3
│
▼
Terminate
問題來了。
Backend 3 裡面的資料可能全部消失:
User A Session → 消失
User B Cart → 消失
Temporary Data → 消失
這代表:
Auto Scaling 不能隨便移除 Backend
系統會變得很難管理。
如果重要狀態都放在外部:
Redis
Database
Object Storage
Backend 本身只有:
Application Code
那麼:
Backend 1
Backend 2
Backend 3
其實就很像三個完全一樣的 Worker。
例如:
Backend 1
├── FastAPI
└── App Code
Backend 2
├── FastAPI
└── App Code
Backend 3
├── FastAPI
└── App Code
真正的資料在:
Redis
Database
Storage
所以 Backend 3 被關掉:
Backend 3 X
也沒有關係。
因為資料還在:
Redis ✓
Database ✓
Storage ✓
下一個 Request 可以直接交給 Backend 1 或 Backend 2。
假設流量突然增加:
CPU = 90%
Auto Scaling 新增:
Backend 4
只要 Backend 4:
使用相同程式
使用相同設定
可以連 Redis
可以連 Database
就可以立刻開始處理 Request。
不需要:
把 Backend 1 的 Session
搬到 Backend 4
也不需要:
同步 Backend 2 的 Memory
因為所有重要狀態本來就在外部服務。
所以:
Load Balancer
│
┌───────┼───────┐
▼ ▼ ▼
Backend 1 Backend 2 Backend 3
流量增加後直接:
Load Balancer
│
┌───────────┼───────────┐
▼ ▼ ▼
Backend 1 Backend 2 Backend 3
│
▼
Backend 4
Backend 5
新 Backend 只要註冊進 Load Balancer,就可以開始接 Request。
目前整體架構就可以變成:
Internet
│
▼
Load Balancer
│
┌────────────┼────────────┐
▼ ▼ ▼
Backend 1 Backend 2 Backend 3
│ │ │
└──────┬─────┴─────┬──────┘
│ │
▼ ▼
Redis Database
Backend 負責:
接收 Request
執行 Business Logic
回傳 Response
Redis 負責:
Session
Cache
Lock
Counter
Temporary State
Database 負責:
正式 Business Data
因此 Backend 本身不需要記住:
上一個 Request
是哪個 User
上一個 Request
由哪台 Server 處理
使用者目前登入在哪台 Server
每一個 Request 都可以獨立處理。
有時候 Stateful 系統暫時無法改成 Stateless,Load Balancer 可以使用:
Sticky Session
意思是:
User A
永遠盡量送到 Backend 1
User B
永遠盡量送到 Backend 2
例如:
Load Balancer
User A ─────────→ Backend 1
User B ─────────→ Backend 2
User C ─────────→ Backend 3
這樣 Backend 1 的 Memory Session 還是可以使用。
但它也有缺點。
如果:
Backend 1 掛掉
User A 原本存在 Backend 1 的狀態仍然可能消失。
而且:
Backend 1 很忙
Backend 2 很空
Load Balancer 也可能因為 Sticky Session,不能自由重新分配流量。
所以 Sticky Session 可以解決部分問題,但不等於真正的 Stateless。
把這兩個概念放在一起,就很好理解:
Stateless
│
▼
Backend 沒有重要本地狀態
│
▼
任何 Backend 都能處理 Request
│
▼
Backend 可以隨時加入或移除
│
▼
Horizontal Scaling
│
▼
Auto Scaling
因此 Stateless 是 Auto Scaling 非常重要的基礎。
如果 Backend 是:
可替換
可複製
彼此等價
不保存重要 Local State
系統才能自由地:
+ Backend
+ Backend
+ Backend
或:
- Backend
- Backend
而不用擔心某一台 Server 消失會把使用者狀態一起帶走。
到這裡,部署觀念已經從:
「我要怎麼把 FastAPI 跑起來?」
逐漸變成:
「我要怎麼讓任何一台 FastAPI
都可以隨時被建立或銷毀?」
這是一個很重要的架構轉變。
最初的部署可能是:
Server A
Nginx
Frontend
FastAPI
Database
之後拆成:
Frontend
Load Balancer
Backend 1
Backend 2
Backend 3
Redis
Database
而 Backend 如果做到 Stateless,就可以進一步做到:
自動建立 Backend
自動移除 Backend
自動部署
自動擴縮
這也就是為什麼接下來會開始出現:
Docker
Container
Image
Container Registry
Kubernetes
Auto Scaling
因為既然 Backend 可以隨時被建立與銷毀,下一個問題自然就會變成:
「我要怎麼快速、穩定地建立一台完全相同的 Backend?」
而這正是 Docker 要解決的其中一個核心問題。